package autoroute

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net"
	"net/http"
	"os"
	"reflect"
	"runtime"
	"strings"
	"time"

	"gitee.com/haodreams/golib/logs"
	"github.com/gin-gonic/gin"
)

// Setup initialize
func Setup(engine *gin.Engine) {
	engine.SetFuncMap(template.FuncMap{
		"str2html": Str2html,
	})
}

// Str2html Convert string to template.HTML type.
func Str2html(raw string) template.HTML {
	return template.HTML(raw)
}

// RunListener 自动选择HTTP 或者 HTTPS服务
// certFile, keyFile 不可用时以http启动，否则 以 tls启动
func RunListener(listener net.Listener, engine *gin.Engine, certFile, keyFile string) (err error) {
	//自动增加内置函数获取当前时间
	engine.GET("/status/now", func(c *gin.Context) {
		c.String(200, time.Now().Format("2006-01-02 15:04:05.999")) //自动获取当前时间，可以用于心跳检测
	})

	//自动增加内置函数获取当前时间
	uptime := time.Now().Format("2006-01-02 15:04:05")
	engine.GET("/status/uptime", func(c *gin.Context) {
		c.String(200, uptime) //自动获取服务启动时间
	})

	useTLS := false
	if certFile != "" && keyFile != "" {
		if _, err := os.Stat(certFile); err == nil {
			if _, err = os.Stat(keyFile); err == nil {
				useTLS = true
			}
		}
	}

	if useTLS { //使用加密
		server := &http.Server{}
		server.Handler = engine
		server.TLSConfig = &tls.Config{
			MinVersion: tls.VersionTLS12, // 设置最小TLS版本为TLS 1.2
			CipherSuites: []uint16{
				// 禁用DES相关的密码套件
				tls.TLS_AES_128_GCM_SHA256,
				tls.TLS_AES_256_GCM_SHA384,
				tls.TLS_CHACHA20_POLY1305_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			},
		}
		err = server.ServeTLS(listener, certFile, keyFile)
	} else {
		err = engine.RunListener(listener)
	}
	return
}

// Normal Get & Post
func Normal(engine *gin.Engine, relativePath string, router AutoRouter, handlers ...gin.HandlerFunc) {
	handlers = append(handlers, AutoRoute(router))
	engine.GET(relativePath, handlers...)
	engine.POST(relativePath, handlers...)
}

// AutoRouter 自动路由接口
type AutoRouter interface {
	//初始化 context
	Setup(c *gin.Context, controllerName string) error
}

func stackTrace() []byte {
	buf := make([]byte, 1024)
	for {
		n := runtime.Stack(buf, false)
		if n < len(buf) {
			return buf[:n]
		}
		buf = make([]byte, 2*len(buf))
	}
}

// StackTrace 栈信息
func StackTrace() string {
	stack := ""
	data := stackTrace()
	lines := strings.Split(string(data), "\n")
	for i := range lines {
		if i > 0 && i < 9 {
			continue
		}
		stack += lines[i] + "\n"
	}
	return stack
}

// GetStackTraceInfo 获取堆栈信息
func GetStackTraceInfo(msg string) (info string) {
	info = fmt.Sprintln("GO Version:", runtime.Version())
	info += fmt.Sprintln("ARCH:", runtime.GOARCH)
	info += fmt.Sprintln("OS:", runtime.GOOS)
	info += fmt.Sprintln("Number CPU:", runtime.NumCPU())
	info += fmt.Sprintln("Routine:", runtime.NumGoroutine())
	m := runtime.MemStats{}
	runtime.ReadMemStats(&m)
	if m.Alloc < 1024 {
		info += fmt.Sprintf("Malloc memory: %d bytes\n", m.Alloc)
	} else if m.Alloc < 1024*1024 {
		info += fmt.Sprintf("Malloc memory: %.3f K bytes\n", float64(m.Alloc)/1024)
	} else {
		info += fmt.Sprintf("Malloc memory: %.3f M bytes\n", float64(m.Alloc)/1024/1024)
	}
	info += "\n" + msg + "\n" + StackTrace()
	return
}

// AutoRoute 自动路由设置，URL 路径名称要小写
// prefix 表示需要关联的前缀
// controller 控制器类
func AutoRoute(controller AutoRouter) func(*gin.Context) {
	typ := reflect.TypeOf(controller)
	elem := typ.Elem()

	controllerName := strings.ToLower(elem.Name())
	controllerName = strings.TrimSuffix(controllerName, "controller")
	m := map[string]reflect.Value{}
	for i := 0; i < typ.NumMethod(); i++ {
		method := typ.Method(i)
		if method.Type.NumOut() != 0 {
			continue
		}
		if method.Type.NumIn() != 1 {
			continue
		}
		methodName := strings.ToLower(method.Name)
		m[methodName] = method.Func
	}

	return func(c *gin.Context) {
		url := c.Param("path")
		url = strings.ToLower(url)
		upath := strings.SplitN(url, "/", 3)
		if len(upath) < 2 {
			c.JSON(http.StatusNotFound, gin.H{
				"code": 404,
				"msg":  "404, page not exists!",
			})
			return
		}
		upath[1] = strings.TrimSuffix(upath[1], ".html")
		if f, ok := m[upath[1]]; ok {
			defer func() {
				if err := recover(); err != nil {
					var brokenPipe bool
					if ne, ok := err.(*net.OpError); ok {
						if se, ok := ne.Err.(*os.SyscallError); ok {
							if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
								brokenPipe = true
							}
						}
					}
					if brokenPipe {
						c.Error(err.(error)) // nolint: errcheck
						c.Abort()
					} else {
						log.Println("AutoRoute() panic:\n", GetStackTraceInfo(fmt.Sprint(err)))
						c.String(501, fmt.Sprint(err)+GetStackTraceInfo(fmt.Sprint(err)))
					}
				}
			}()

			in := make([]reflect.Value, 1)
			obj := reflect.New(elem)
			route, ok := obj.Interface().(AutoRouter)
			if !ok {
				log.Println("error type")
				return
			}
			err := route.Setup(c, controllerName)
			if err != nil {
				if c.Writer.Status() != 0 {
					return
				}
				c.JSON(http.StatusPreconditionFailed, gin.H{
					"code": 412,
					"msg":  err.Error(),
				})
				return
			}
			in[0] = obj
			f.Call(in)
			return
		}
		c.JSON(http.StatusNotFound, gin.H{
			"code": 404,
			"msg":  "404, page not exists!",
		})
	}
}

/**
 * @description: 生成json数据
 * @param {*gin.Context} c
 * @param {interface{}} data
 * @param {string} msg
 * @param {...int} code
 * @return {*}
 */
func DataMsg(c *gin.Context, data interface{}, msg string, code ...int) {
	m := map[string]any{}
	m["data"] = data
	m["msg"] = msg
	if len(code) > 0 {
		m["code"] = code[0]
	} else {
		m["code"] = http.StatusOK
	}
	c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
	jsonBytes, err := json.Marshal(m)
	if err != nil {
		logs.Warn(err)
		return
	}
	_, err = c.Writer.Write(jsonBytes)
	if err != nil {
		logs.Warn(err)
		return
	}
}

func DefaultUploadHtml(writer gin.ResponseWriter, extend string) {
	html := `<!DOCTYPE html>
	<html> 
	<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>上传文件</title></head>
	<body><form method="post" enctype="multipart/form-data">
	%s
	<input type="file" name="file" value="" /> 	<input type="submit" name="submit" />
	</form></body></html>`

	writer.Header().Add("Content-Type", "text/html")
	writer.WriteHeader(200)
	writer.Write([]byte(fmt.Sprintf(html, extend)))
}
